WebGL 齐次坐标与模型矩阵变换
一句话结论:齐次坐标让平移变成矩阵乘法,模型矩阵负责把顶点从模型自身坐标搬到场景坐标,两者是所有顶点变换的起点。
齐次坐标与四维向量
是什么
齐次坐标是在三维坐标之外增加一个 w 分量的四维表示法,点写作 vec4(x, y, z, 1.0),方向向量写作 vec4(x, y, z, 0.0)。WebGL 中顶点着色器的 gl_Position 就是裁剪空间下的四维齐次坐标。
解决什么问题
它解决平移无法用矩阵乘法表达的问题。纯三维的线性变换只能表达旋转与缩放,平移需要额外加法;引入 w 分量后,平移可以写进 4x4 矩阵的最后一列,于是旋转、缩放、平移、投影全部统一为一次矩阵乘法,可以任意串联。
为什么这样做
GPU 的顶点处理单元擅长一次矩阵乘向量运算。把变换统一成矩阵乘法,就能把缩放、旋转、平移、视图、投影预先相乘成一个矩阵,顶点着色器里只做一次乘法,把多次运算压缩为一次,这是顶点变换性能的关键。
怎么用
attribute vec3 aPosition;
uniform mat4 uModelMatrix;
uniform mat4 uViewProjectionMatrix;
void main() {
// 点的 w 为 1,方向向量为 0,二者变换规则不同
gl_Position = uViewProjectionMatrix * uModelMatrix * vec4(aPosition, 1.0);
}
// 平移写进矩阵最后一列,因此能被乘法表达
const T = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1, // 列主序:最后一列存放平移量
];
常见坑
- 用 vec4(aPosition, 0.0) 表示顶点位置 → 原因:w 为 0 表示方向向量,无法平移 → 处理:位置一律补 1.0
- 手动实现透视除法 → 原因:GPU 在裁剪后自动执行除法 → 处理:着色器里不要自己除以 w
- 误以为 w 恒等于 1 → 原因:投影矩阵的 w 会被改写 → 处理:透视投影下 w 通常等于负的视空间 z
模型矩阵与顶点变换
是什么
模型矩阵是把顶点从模型局部坐标系变换到世界坐标系的 4x4 矩阵。它描述物体在场景中的位置、朝向与大小,通常由缩放、旋转、平移三个基础变换按固定顺序相乘得到。
解决什么问题
它解决模型复用与场景摆放问题。同一个立方体模型只需要一份顶点数据,通过不同的模型矩阵就能出现在场景的不同位置、以不同角度和大小呈现,避免为每个实例复制顶点。
为什么这样做
顶点数据应当只描述形状,摆放属于运行时状态。把摆放信息放进矩阵,顶点缓冲就可以被多个物体共享,绘制时只需切换 uniform,无需重传顶点数据,这也是批量绘制与实例化渲染的基础。
怎么用
// 严格按 缩放 → 旋转 → 平移 的顺序拼接
const model = mat4.create();
mat4.identity(model);
mat4.translate(model, model, [x, y, z]); // 先写平移,最后作用于顶点
mat4.rotateY(model, model, angle); // 再旋转
mat4.scale(model, model, [sx, sy, sz]); // 最后缩放
gl.uniformMatrix4fv(uModel, false, model);
| 变换顺序 | 结果 |
|---|---|
| 缩放 → 旋转 → 平移(推荐) | 围绕自身轴缩放旋转,再整体移动 |
| 平移 → 旋转 | 物体绕世界原点公转,位置偏移 |
| 旋转 → 缩放(非等比) | 几何体被沿旋转后的轴拉伸,出现剪切 |
常见坑
- 矩阵拼接顺序写反 → 原因:矩阵乘法不满足交换律 → 处理:固定使用缩放、旋转、平移顺序
- 每帧重复计算静态矩阵 → 原因:CPU 端浪费 → 处理:静态物体的模型矩阵只算一次并缓存
- 忘记更新 uniform → 原因:切换物体后仍用上一个矩阵 → 处理:每次绘制前重新设置 uModel
- 用逐顶点方式做物体位移 → 原因:改动顶点数据开销大 → 处理:位移全部交给模型矩阵
要点总结
- 齐次坐标用 w 分量把平移写成矩阵乘法,位置补 1.0、方向补 0.0
- GPU 自动执行透视除法,w 由投影矩阵决定
- 模型矩阵把顶点从局部坐标变换到世界坐标,决定摆放位置与姿态
- 矩阵乘法不交换,模型矩阵固定按缩放、旋转、平移拼接
- 位移应交给矩阵而非修改顶点数据,静态矩阵要缓存复用
